# mvautotest.pl

use strict;
use Config::INIPlus;
use Cwd;
use File::Path;
use Email::Send;


use constant CRIT	=>	1;
use constant ERR	=>	2;
use constant WARN	=>	3;
use constant INFO	=>	4;
use constant VERB	=>	5;
use constant LOG_SIGN => [ "OFF ", "CRIT", "ERR ", "WARN", "INFO", "VERB" ];

use constant CHECKOUT_FAILED	=>	1;
use constant BUILD_FAILED		=>	2;
use constant TEST_FAILED		=>	3;
use constant SUMMARY			=>	4;

### Declarations ###
sub ReadMainConfig();
sub SearchPluginsInDir( $$ );
sub SearchScripts();
sub Log( $$ );
sub InitEnv();


### Definations ###
my $log_file = "mvautotest.log";
my $log_file_handle;
my %config;
my @output_buffer;
my %checkout_scripts_map;
my %build_scripts_map;
my %test_scripts_map;
my %scripts_map;


### Main Functions ###

$config{"log_level"} = CRIT;
open( $log_file_handle, ">>", $log_file ) or die "Can not open log file $log_file, $!";

Log( CRIT, "MVAutoTest Started" );
ReadMainConfig();
InitEnv();
BeginTest();

### Functions ###
sub ReadMainConfig()
{
	
	my $cfg = Config::INIPlus->new( file => "defines.cfg" );

	$config{"interval"} = $cfg->get( "INTERVAL", "MAIN" );
	$config{"log_level"} = $cfg->get( "LOG_LEVEL", "MAIN" );

	$config{"root_dir"} = $cfg->get( "ROOT_DIR", "DIR" );
	my $root_dir_full_path = Cwd::abs_path( $config{"root_dir"} );
	$root_dir_full_path =~ s/\//\\/g;
	$config{"root_dir"} = $root_dir_full_path;
	$config{"bin_dir"} = $config{"root_dir"} . "\\" . $cfg->get( "BIN_DIR", "DIR" );
	$config{"build_scripts_dir"} = $config{"root_dir"} . "\\" . $cfg->get( "BUILD_SCRIPTS_DIR", "DIR" );
	$config{"checkout_scripts_dir"} = $config{"root_dir"} . "\\" . $cfg->get( "CHECKOUT_SCRIPTS_DIR", "DIR" );
	$config{"test_scripts_dir"} = $config{"root_dir"} . "\\" . $cfg->get( "TEST_SCRIPTS_DIR", "DIR" );
	$config{"scripts_dir"} = $config{"root_dir"} . "\\" . $cfg->get( "SCRIPTS_DIR", "DIR" );
	$config{"temp_dir"} = $config{"root_dir"} . "\\" . $cfg->get( "TEMP_DIR", "DIR" );

	$config{"smtp_server"} = $cfg->get( "SMTP_SERVER", "MAIL" );
	$config{"user"} = $cfg->get( "USER", "MAIL" );
	$config{"pass"} = $cfg->get( "PASS", "MAIL" );
	$config{"reply_address"} = $cfg->get( "REPLY_ADDRESS", "MAIL" );
	$config{"subject"} = $cfg->get( "SUBJECT", "MAIL" );

	Log( INFO, "Read configuration from file: defines.cfg" );

	my $key;
	foreach $key ( keys( %config ) )
	{
		Log( VERB, "$key => $config{$key}" );
	}

}

# $in plugin suffix
# $in directory to search
# %out name to file map, if none return undef
sub SearchPluginsInDir( $$ )
{
	my $plugin_suffix = shift @_;
	my $search_dir = shift @_;
	my %name_file_map;
	my $dir_handle;

	Log( INFO, "Searching plugins in directory: $search_dir" );

	unless( opendir( $dir_handle, $search_dir ) )
	{
		Log( ERR, "Can't open directory: $search_dir." );
		return undef;
	}


	# Get the file list and turn them into "full" path
	my @plugin_files = map { "$search_dir\\$_" } grep { /\.$plugin_suffix$/ } readdir( $dir_handle );

	if( scalar(@plugin_files) == 0 )
	{
		return undef;
	}

	# open every file to check the plugin name
	my $file;
	foreach $file ( @plugin_files )
	{
		my $file_handle;
		unless( open( $file_handle, "<$file" ) )
		{
			Log( ERR, "error in open $file" );
			next;
		}

		my $first_line = <$file_handle>;

		chomp $first_line;

		if( $first_line =~ /PLUGIN_NAME\s+=\s+(.*)/ )
		{
			my $value = $1;
			$name_file_map{$value} = $file;
		}
	}

	my $temp;
	foreach $temp ( keys( %name_file_map ) )
	{
		Log( VERB, "$temp => $name_file_map{$temp}" );
	}

	return %name_file_map;

}

# @in Log Level
# @in Log Message
sub Log( $$ )
{
	my $level = shift @_;
	my $message = shift @_;

	if( $config{"log_level"} >= $level )
	{
		print LOG_SIGN->[$level], ": $message\n";
		print $log_file_handle LOG_SIGN->[$level], ": $message\n";
	}

}

# %out scripts to file path map, return undef if none exists
sub SearchScripts()
{
	my %name_file_map;
	my $dir_handle;
	my $search_dir = $config{"scripts_dir"};

	Log( INFO, "Searching scripts in directory: $search_dir" );

	unless( opendir( $dir_handle, $search_dir ) )
	{
		Log( ERR, "Can't open directory: $search_dir." );
		return undef;
	}


	# Get the file list and turn them into "full" path
	my @script_files = grep { /\.script$/ } readdir( $dir_handle );
	if( scalar(@script_files) == 0 )
	{
		return undef;
	}

	%name_file_map = map { substr( $_, 0, -7 ) => "$search_dir\\$_" } @script_files;

	my $temp;
	foreach $temp ( keys( %name_file_map ) )
	{
		Log( VERB, "$temp => $name_file_map{$temp}" );
	}

	return %name_file_map;

}

# %out test scripts to file path map, return undef if none exits
sub SearchTestScripts()
{
	my %name_file_map;
	my $dir_handle;
	my $search_dir = $config{"test_scripts_dir"};

	Log( INFO, "Searching test scripts in directory: $search_dir" );

	unless( opendir( $dir_handle, $search_dir ) )
	{
		Log( ERR, "Can't open directory: $search_dir." );
		return undef;
	}


	# Get the file list and turn them into "full" path
	my @script_dir = grep{ !/^\./ } grep{ -d "$search_dir\\$_" } readdir( $dir_handle );
	if( scalar(@script_dir) == 0 )
	{
		return undef;
	}

	%name_file_map = map { $_ => "$search_dir\\$_" } @script_dir;

	my $temp;
	foreach $temp ( keys( %name_file_map ) )
	{
		Log( VERB, "$temp => $name_file_map{$temp}" );
	}

	return %name_file_map;

}


sub InitEnv()
{
	Log( INFO, "Check temporary directory" );
	unless( -d $config{"temp_dir"} )
	{
		Log( VERB, "Temporary directory does not exist, create it." );
		my @created_dirs = mkpath( $config{"temp_dir"} );
		if( scalar( @created_dirs ) == 0 )
		{
			Log( ERR, "Failed to create temporary directory." );
			exit 1;
		}
	}

	Log( INFO, "Search checkout scripts." );
	%checkout_scripts_map = SearchPluginsInDir( "pl", $config{"checkout_scripts_dir"} );
	unless( defined %checkout_scripts_map )
	{
		Log( CRIT, "No checkout script found, exiting." );
		exit( 1 );
	}

	Log( INFO, "Search build scripts." );
	%build_scripts_map = SearchPluginsInDir( "pl", $config{"build_scripts_dir"} );
	unless( defined %build_scripts_map )
	{
		Log( CRIT, "No build script found, exiting." );
		exit( 1 );
	}

	Log( INFO, "Search test scripts." );
	%test_scripts_map = SearchTestScripts();
	unless( defined %test_scripts_map )
	{
		Log( CRIT, "No test script found, exiting." );
		exit( 1 );
	}

	Log( INFO, "Search scripts." );
	%scripts_map = SearchScripts();
	unless( defined %scripts_map )
	{
		Log( CRIT, "No script found, exiting." );
		exit( 1 );
	}

}

sub BeginTest()
{

	my $script;
	foreach $script ( keys( %scripts_map ) )
	{
		# DEBUG
		my $result = 0;

		my %script_config = ReadScript( $scripts_map{$script} );
		unless( defined %script_config )
		{
			Log( ERR, "Error in reading script." );
			next;
		}

		$script_config{"name"} = $script;

		# DEBUG
		$result = Checkout( \%script_config );

		if( $result == 2 )
		{
			Log( VERB, "No change has been made, skip." );
			next;
		}
		elsif( $result == 1 )
		{
			Log( ERR, "Checkout failed." );
			SendMail( CHECKOUT_FAILED, \%script_config, "Failed to checkout project " . $script_config{"name"} );
			next;
		}
		else
		{
			Log( VERB, "Checkout succeessfully." );
		}

		# DEBUG
		$result = Build( \%script_config );

		if( $result == 1 )
		{
			Log( ERR, "Build failed." );
			SendMail( BUILD_FAILED, \%script_config,  "Failed to build project " . $script_config{"name"} );
			next;
		}
		else
		{
			Log( VERB, "Build succeessfully." );
		}
		
		$result = Test( \%script_config );

		if( $result == 1 )
		{
			Log( ERR, "Test failed." );
			SendMail( TEST_FAILED, \%script_config,  "Project " . $script_config{"name"} . " failed to pass the tests." );
			next;
		}

		SendMail( SUMMARY, \%script_config, "Project " .  $script_config{"name"} . " runs well. All tests has been passed." );
	}

}


# %out script configuration, undef if error occurs
sub ReadScript( $ )
{
	my $script_file_path = shift @_;
	my %script_config;

	Log( VERB, "Read Script: $script_file_path" );

	my $cfg = Config::INIPlus->new( file => $script_file_path );
	my @owners = split /:/, $cfg->get( "OWNERS", "MAIN" );
	$script_config{"owners"} = \@owners;

	$script_config{"checkout_method"} = $cfg->get( "CHECKOUT_METHOD", "CHECKOUT" );
	my $checkout_method = $script_config{"checkout_method"};
	unless( defined $checkout_scripts_map{$checkout_method} )
	{
		Log( ERR, "Unknown checkout method: $checkout_method." );
		return undef;
	}

	$script_config{"checkout_path"} = $cfg->get( "CHECKOUT_PATH", "CHECKOUT" );

	$script_config{"build_method"} = $cfg->get( "BUILD_METHOD", "BUILD" );
	my $build_method = $script_config{"build_method"};
	unless( defined $build_scripts_map{$build_method} )
	{
		Log( ERR, "Unknown build method: $build_method." );
		return undef;
	}

	$script_config{"build_path"} = $cfg->get( "BUILD_PATH", "BUILD" );

	my @test_scripts = split /:/, $cfg->get( "TEST_SCRIPTS", "TEST" );
	$script_config{"test_scripts"} = \@test_scripts;

	return %script_config;
}

# $in reference to script configuration
# $out 0 for succeeded, 1 for failed, 2 for skiped
sub Checkout( $ )
{
	my $ref_script_config = shift @_;
	my %script_config = %$ref_script_config;

	my $cmd = "perl";
	$cmd .= " " . "\"" . $checkout_scripts_map{$script_config{"checkout_method"}} . "\"";
	$cmd .= " CHECKOUT";
	$cmd .= " " . $script_config{"checkout_path"};
	$cmd .= " " . $script_config{"name"};
	$cmd .= " " . "\"" . $config{"temp_dir"} . "\"";
	
	Log( VERB, "Checkout command: $cmd" );

	open( OUTPUT, "$cmd|" );

	undef @output_buffer;

	my $line;
	my $succeeded = 0;
	while ( $line = <OUTPUT> )
	{
		chomp $line;

		if( $line =~ /OUTPUT> (.*)/ )
		{
			push @output_buffer, $1;
		}
		elsif ( $line =~ /SUCCEEDED/ )
		{
			return 0;
		}
		elsif ( $line =~ /FAILED/ )
		{
			return 1;
		}
		elsif ( $line =~ /SKIP/ )
		{
			return 2;
		}
	}

}


# $in reference to script configuration
# $out 0 for succeeded, 1 for failed
sub Build( $ )
{

	my $ref_script_config = shift @_;
	my %script_config = %$ref_script_config;

	my $build_path = $config{"temp_dir"} . "\\" .  $script_config{"name"} . "\\" . $script_config{"build_path"};

	my $cmd = "perl";
	$cmd .= " " . "\"" . $build_scripts_map{$script_config{"build_method"}} . "\"";
	$cmd .= " BUILD";
	$cmd .= " " . "\"" . $build_path . "\"";

	
	Log( VERB, "Build command: $cmd" );

	open( OUTPUT, "$cmd|" );

	undef @output_buffer;

	my $line;
	while ( $line = <OUTPUT> )
	{
		chomp $line;

		if( $line =~ /OUTPUT> (.*)/ )
		{
			push @output_buffer, $1;
		}
		elsif ( $line =~ /TOTAL (\d+) SUCCEEDED (\d+) FAILED (\d+) SKIPPED/ )
		{
			if( $2 == 0 && $1 != 0 )
			{
				return 0;
			}
			else
			{
				return 1;
			}
		}
	}

	return 1;

}

# $in reference to script configuration
# $out 0 for succeeded, 1 for failed
sub Test( $ )
{
	my $ref_script_config = shift @_;
	my %script_config = %$ref_script_config;

	my $project_dir = $config{"temp_dir"} . "\\" .  $script_config{"name"};

	my @test_script_list = @{$script_config{"test_scripts"}};

	undef @output_buffer;

	my $test_script_name;
	my $all_succeeded = 1;

	foreach $test_script_name ( @test_script_list )
	{
		unless( defined $test_scripts_map{$test_script_name} )
		{
			Log( ERR, "Test script $test_script_name doesnot exist." );
			push @output_buffer, "Test script $test_script_name doesnot exist.";
			push @output_buffer, "";
			next;
		}

		chdir( $config{"test_scripts_dir"} . "\\" . $test_script_name );

		unless( -f "test.pl" )
		{
			Log( ERR, "test.pl in test script $test_script_name doesnot exist." );
			push @output_buffer, "test.pl in test script $test_script_name doesnot exist.";
			push @output_buffer, "";
			next;
		}

		my $cmd = "perl test.pl";
		$cmd .= " " . "\"" . $project_dir . "\"";
		Log( VERB, "Test command: $cmd" );

		open( OUTPUT, "$cmd|" );

		my $line;
		my @temp_buffer;
		while ( $line = <OUTPUT> )
		{
			chomp $line;
			print "$line\n";

			if( $line =~ /OUTPUT> (.*)/ )
			{
				push @temp_buffer, $1;
			}
			elsif ( $line =~ /SUCCEEDED/ )
			{
				push @output_buffer, "( V ) Test script $test_script_name passed.";
				push @output_buffer, @temp_buffer;
				push @output_buffer, "";
			}
			elsif ( $line =~ /FAILED/ )
			{
				push @output_buffer, "( X ) Test script $test_script_name failed.";
				push @output_buffer, @temp_buffer;
				push @output_buffer, "";
				$all_succeeded = 0;
			}

		}
	}

	if ( $all_succeeded )
	{
		return 0;
	}
	else
	{
		return 1;
	}

}

# $in Mail Type 
# $in reference to script configuration
# $in message
sub SendMail ( $$$ )
{

	# DEBUG
	#return 0;
	my $mail_type = shift @_;
	my $ref_script_config = shift @_;
	my $message = shift @_;
	my %script_config = %$ref_script_config;

	my $receivers = join ";", @{$script_config{"owners"}};
	my $mail_text = "To: $receivers\n";
	$mail_text .= "From: " . $config{"reply_address"} . "\n";
	$mail_text .= "Subject: " . $config{"subject"} . "\n\n";
	$mail_text .= "$message\n\n";

	if( $mail_type != SUMMARY )
	{
		$mail_text .= "======= Detail of errors ======\n";
		my $log = join "\n", @output_buffer;
		$mail_text .= $log;
	}

	my $sender = Email::Send->new( { mailer => 'SMTP' } );
	$sender->mailer_args( [ Host => $config{"smtp_server"}, Hello => "mvautotest", username => $config{"user"}, password => $config{"pass"} ] );
	my $result = $sender->send($mail_text);
	if ( $result )
	{
		Log( INFO, "Mail send successfully." );
	}
	else
	{
		Log( CRIT, "Mail send failed. Reason: $result" );
	}

}


